package com.base.springboot.starter.log.config;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 解析MethodINfoLogUtil注解，并对方法执行的入参、结果进行日志输出
 * <p>
 * 如果配合MethodInvocationLogUtil一起使用
 *
 * @author admin
 * @date 2020/5/20 PM12:02
 */
@Slf4j
@Aspect
@Order(12)
@Component
public class MethodInvocationByAnn extends AbstractMethodInvocation {
    @Pointcut("@annotation(com.base.springboot.starter.log.config.MethodInfoLogUtil)")
    public void logPointCut() {
        // not thing to do
    }

    @Around("logPointCut()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        long timeFlag1 = System.currentTimeMillis();
        //获取注解对象
        MethodInfoLogUtil util = this.getMethodInfo(pjp);
        long timeFlag2 = System.currentTimeMillis();
        //输出请求参数
        this.argsToLog(pjp, util);
        long timeFlag3 = System.currentTimeMillis();
        long timeFlag4 = -1;
        long timeFlag5 = -1;

        Object result = null;
        try {
            //执行目标方法
            result = pjp.proceed();
            timeFlag4 = System.currentTimeMillis();
            //输出方法执行结果日志
            this.resultToLog(util, result);
            timeFlag5 = System.currentTimeMillis();
        } catch (Exception e) {
            timeFlag4 = System.currentTimeMillis();
            this.exceptionToLog(util, e);
            timeFlag5 = System.currentTimeMillis();
            //执行方法发生异常后向上正常抛出
            throw e;
        } finally {
            if (log.isDebugEnabled()) {
                log.debug("[framework-common][method], startTime={}; getMethodInfo={}; argsToLog={}; proxy={}; resultToLog={}",
                        new Object[]{timeFlag1, timeFlag2 - timeFlag1, timeFlag3 - timeFlag2, timeFlag4 - timeFlag3, timeFlag5 - timeFlag4});
            }
        }
        return result;
    }

    /**
     * <p>Title: getMethodInfo.</p>
     * <p>Description: 获取方法上的MethodInfoLogUtil注解对象</p>
     *
     * @param pjp ProceedingJoinPoint
     * @return 注解对象，如果没有注解描述，返回null
     */
    private MethodInfoLogUtil getMethodInfo(ProceedingJoinPoint pjp) {
        try {
            MethodSignature signature = (MethodSignature) pjp.getSignature();
            Method method = signature.getMethod();
            //方法没有使用注解进行描述
            if (!method.isAnnotationPresent(MethodInfoLogUtil.class)) {
                return null;
            }
            return method.getAnnotation(MethodInfoLogUtil.class);
        } catch (Exception e) {
            log.error("[framework-common][method], desc={}, 获取注解对象发生异常：{}", e.getMessage(), e);
            return null;
        }
    }

    /**
     * <p>Title: argsToLog.</p>
     * <p>Description: 将参数输出到日志中，使用info级别 </p>
     *
     * @param pjp  切面对象
     * @param util 注解对象
     */
    private void argsToLog(ProceedingJoinPoint pjp, MethodInfoLogUtil util) {
        //未设置注解对象或设置取值为false不需要输出
        if (util == null || !util.args()) {
            return;
        }

        try {
            log.info("[framework-common][method], desc={}, args={}", util.desc(), getArgsValue(pjp.getArgs()));
        } catch (Exception e) {
            log.error("[framework-common][method], desc={}, 请求参数输出到日志发生异常：{}", util.desc(), e.getMessage(), e);
        }
    }

    /**
     * <p>Title: resultToLog.</p>
     * <p>Description: 将结果使用json的方式输出到日志中，使用info级别 </p>
     *
     * @param util   注解对象
     * @param result 执行结果对象
     */
    private void resultToLog(MethodInfoLogUtil util, Object result) {
        //未设置注解对象，或设置取值为false，不需要输出
        if (null == util || !util.result()) {
            return;
        }
        try {
            log.info("[framework-common][method], desc={}, result={}", util.desc(), getResultValue(result));
        } catch (Exception e) {
            log.error("[framework-common][method], desc={}, 输出result发生错误: {}", util.desc(), e.getMessage(), e);
        }

    }

    /**
     * <p>Title: exceptionToLog.</p>
     * <p>Description: 将异常信息记录到日志 </p>
     *
     * @param util 注解对象
     * @param e    异常信息
     */
    private void exceptionToLog(MethodInfoLogUtil util, Exception e) {
        if (null == util || !util.exception()) {
            return;
        }
        log.error("[framework-common][method], desc={}, error: {}", util.desc(), e.getMessage(), e);
    }
}
